﻿// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.

#define _CRT_SECURE_NO_WARNINGS

#include "FileList.h"

//
using namespace std;

//
DWORD g_dwLogThread = 0;

//
DWORD Now()
{
	return timeGetTime();
}

void Log(const WCHAR* format, ...)
{
	WCHAR* buffer = nullptr;
	size_t size = 0;
	int len = 0;

	//
	va_list va;
	va_start(va, format);
	len = _vsnwprintf(buffer, size, format, va) + 1;
	buffer = new WCHAR[len];
	len = _vsnwprintf(buffer, len, format, va);

	{
		WCHAR* szLog = new WCHAR[len + 32];
		time_t rawtime;
		time(&rawtime);
		struct tm* ltm = localtime(&rawtime);
		wsprintfW(szLog, L"\n%04d-%02d-%02d %02d:%02d:%02d %s",
			ltm->tm_year + 1900,
			ltm->tm_mon + 1,
			ltm->tm_mday,
			ltm->tm_hour,
			ltm->tm_min,
			ltm->tm_sec,
			buffer);

		BOOL b = PostThreadMessageW(g_dwLogThread, MSG_LOGW, 0, (LPARAM)szLog);
		if (!b)
		{
			::OutputDebugStringW(L"");
		}

		//
		//delete[] szLog;
	}

	//
	delete[] buffer;

	va_end(va);
}

void Log(const CHAR* format, ...)
{
	CHAR* buffer = nullptr;
	size_t size = 0;
	int len = 0;

	//
	va_list va;
	va_start(va, format);
	len = _vsnprintf(buffer, size, format, va) + 1;
	buffer = new CHAR[len];
	len = _vsnprintf(buffer, len, format, va);

	{
		CHAR* szLog = new CHAR[len + 32];
		time_t rawtime;
		time(&rawtime);
		struct tm* ltm = localtime(&rawtime);
		wsprintfA(szLog, "\n%04d-%02d-%02d %02d:%02d:%02d %s",
			ltm->tm_year + 1900,
			ltm->tm_mon + 1,
			ltm->tm_mday,
			ltm->tm_hour,
			ltm->tm_min,
			ltm->tm_sec,
			buffer);

		PostThreadMessageA(g_dwLogThread, MSG_LOGA, 0, (LPARAM)szLog);

		//
		//delete[] szLog;
	}

	//
	delete[] buffer;

	va_end(va);
}

DWORD LogProc(LPVOID lParam)
{
	MSG msg;
	while (GetMessage(&msg, 0, 0, 0))
	{
		if (msg.message == MSG_LOGW)
		{
			WCHAR* log = (WCHAR*)msg.lParam;
			::OutputDebugStringW(log);

			//
			delete[] log;
		}
		else if (msg.message == MSG_LOGA)
		{
			CHAR* log = (CHAR*)msg.lParam;
			::OutputDebugStringA(log);

			//
			delete[] log;
		}
		else
		{

		}
		DispatchMessage(&msg);
	}

	return 0;
}

//
CHAR* WChar2Char(WCHAR szStr[])
{
	int nLen = WideCharToMultiByte(CP_ACP, 0, szStr, -1, NULL, 0, NULL, NULL);
	if (nLen == 0)
		return "";

	CHAR* szResult = new CHAR[nLen + 1];
	memset(szResult, 0, nLen + 1);

	WideCharToMultiByte(CP_ACP, 0, szStr, -1, szResult, nLen, NULL, NULL);

	//
	return szResult;
}

//
TTask::~TTask()
{
	delete[] szName;
}

//
void MyQueueThread::ProcessQueue(MQueueParam *lpQueueParam)
{
	TTask* task = (TTask*)lpQueueParam->m_dwParam1;
	if (task)
	{
		task->listVolume->DoTask(task);
	}
}

//
TFileInfo::TFileInfo()
{
	strcpy(szVolume, "D:");
	fType = eUNKNOWN;
	iParentFileReferenceNumber = ROOT_FILE_REF_NO;
	iFileReferenceNumber = ROOT_FILE_REF_NO;
	iSize = 0;
	iFileAttributes = 0;
	bFullPath = FALSE;

	//
	szName = NULL;
	szPath = NULL;
}

TFileInfo::~TFileInfo()
{
	if (szName)
		delete[] szName;

	if (szPath)
		delete[] szPath;
}

//
CListVolume::CListVolume()
{
	m_queueThread.StartThread(0);
}

CListVolume::~CListVolume()
{
}

int CListVolume::PostTask(TTask* task)
{
	m_queueThread.ThreadQueue(QUEUE_ADD, (ULONGLONG)task);

	return 0;
}

int CListVolume::DoTask(TTask* task)
{
	if (task->iType == 1)
	{
		return 0;
	}
	else if(task->iType == 0)
	{
		return 0;
	}

	return 0;
}

void CListVolume::AddFile(TFileInfo* _fileInfo)
{
	//
	FileInfoMap::iterator it = m_mapFileInfo.find(_fileInfo->iFileReferenceNumber);
	if (it != m_mapFileInfo.end())
	{
		FileInfoVector* vec = it->second;
		vec->push_back(_fileInfo);
	}
	else
	{
		FileInfoVector* vec = new FileInfoVector;
		vec->push_back(_fileInfo);
		m_mapFileInfo.insert(pair<UInt64, FileInfoVector*>(_fileInfo->iFileReferenceNumber, vec));
	}

	m_vecFileInfo.push_back(_fileInfo);
}

void CListVolume::AddFolder(TFileInfo* _fileInfo)
{
	//
	FileInfoMap::iterator it = m_mapFolderInfo.find(_fileInfo->iFileReferenceNumber);
	if (it != m_mapFolderInfo.end())
	{
		FileInfoVector* vec = it->second;
		vec->push_back(_fileInfo);
	}
	else
	{
		FileInfoVector* vec = new FileInfoVector;
		vec->push_back(_fileInfo);
		m_mapFolderInfo.insert(pair<UInt64, FileInfoVector*>(_fileInfo->iFileReferenceNumber, vec));
	}

	m_vecFolderInfo.push_back(_fileInfo);
}

UInt64 CListVolume::GetFileSize(CHAR szFile[])
{
	CHAR szTempPath[PATH_LEN] = { 0 };
	CHAR szPath[PATH_LEN] = { 0 };
	strcpy(szPath, "\\\\?\\"); // 适配超长路径（大于260个字节）
	strcat(szPath, szFile);

	//
	UInt64 iSize = 0;
	WIN32_FILE_ATTRIBUTE_DATA fileAttr;
	BOOL b = ::GetFileAttributesExA(szPath, GetFileExInfoStandard, &fileAttr);
	if (!b)
	{
		//
		DWORD dwError = ::GetLastError();
		Log("GetFileSize() GetFileAttributesExW() Error Code %d. %d. %s", dwError, strlen(szPath), szPath);

		iSize = 0;
	}
	else
	{
		iSize = ((UInt64)fileAttr.nFileSizeHigh << 32) + fileAttr.nFileSizeLow;
	}

	return iSize;
}

int CListVolume::BuildFolderPath()
{
	//
	CHAR szPath[PATH_LEN] = { 0 };
	CHAR szTempPath[PATH_LEN] = { 0 };

	//
	FileInfoMap::iterator it = m_mapFolderInfo.begin();
	for (; it != m_mapFolderInfo.end(); it++)
	{
		memset(szPath, 0, PATH_LEN);
		memset(szTempPath, 0, PATH_LEN);

		FileInfoVector* vec = it->second;
		TFileInfo* _fileInfo = vec->at(0);

		if (_fileInfo->iParentFileReferenceNumber == ROOT_FILE_REF_NO)
		{
			strcpy(szPath, _fileInfo->szVolume);
			strcat(szPath, "\\");
			strcat(szPath, _fileInfo->szName);

			//
			UInt iLen = strlen(szPath) + strlen(_fileInfo->szName) + 1;
			_fileInfo->szPath = new CHAR[iLen];
			memset(_fileInfo->szPath, 0, iLen);
			strcpy(_fileInfo->szPath, szPath);

			_fileInfo->bFullPath = TRUE;
		}
		else
		{
			FileInfoMap::iterator itParent = m_mapFolderInfo.find(_fileInfo->iParentFileReferenceNumber);
			while (itParent != m_mapFolderInfo.end())
			{
				FileInfoVector* vec = itParent->second;
				TFileInfo* fileInfo = vec->at(0);

				memset(szTempPath, 0, PATH_LEN);

				if (fileInfo->iParentFileReferenceNumber == ROOT_FILE_REF_NO)
				{
					strcpy(szTempPath, fileInfo->szName);
					strcat(szTempPath, "\\");
					strcat(szTempPath, szPath);
					strcpy(szPath, szTempPath);

					strcpy(szTempPath, fileInfo->szVolume);
					strcat(szTempPath, "\\");
					strcat(szTempPath, szPath);
					strcpy(szPath, szTempPath);

					_fileInfo->bFullPath = TRUE;

					break;
				}
				else
				{
					strcpy(szTempPath, fileInfo->szName);
					strcat(szTempPath, "\\");
					strcat(szTempPath, szPath);
					strcpy(szPath, szTempPath);
				}

				itParent = m_mapFolderInfo.find(fileInfo->iParentFileReferenceNumber);
			}

			UInt iLen = strlen(szPath) + strlen("\\") + strlen(_fileInfo->szName) + 1;
			_fileInfo->szPath = new CHAR[iLen];
			memset(_fileInfo->szPath, 0, iLen);
			strcpy(_fileInfo->szPath, szPath);
			strcat(_fileInfo->szPath, _fileInfo->szName);
		}
	}

	return 0;
}

void CListVolume::BuildFilePath()
{
	CHAR szPath[PATH_LEN] = { 0 };
	FileInfoMap::iterator it = m_mapFileInfo.begin();
	for (; it != m_mapFileInfo.end(); it++)
	{
		TFileInfo* _fileInfo = it->second->at(0);
		if (_fileInfo->iParentFileReferenceNumber == ROOT_FILE_REF_NO)
		{
			memset(szPath, 0, PATH_LEN);
			strcpy(szPath, _fileInfo->szVolume);
			strcat(szPath, "\\");
			strcat(szPath, _fileInfo->szName);

			// TODO szPath to Sqlite db.
			//dbInsert(_fileInfo->iFileReferenceNumber, szPath);
			TTask* task = new TTask();
			task->listVolume = this;
			task->iFileRefNum = _fileInfo->iFileReferenceNumber;
			task->szName = new CHAR[strlen(szPath)+1];
			strcpy(task->szName, szPath);
			PostTask(task);

			_fileInfo->bFullPath = TRUE;

			delete _fileInfo;

			continue;
		}

		FileInfoMap::iterator itPath = m_mapFolderInfo.find(_fileInfo->iParentFileReferenceNumber);
		if (itPath != m_mapFolderInfo.end())
		{
			FileInfoVector* vec = itPath->second;
			TFileInfo* fileInfo = vec->at(0);

			memset(szPath, 0, PATH_LEN);
			strcpy(szPath, fileInfo->szPath);
			strcat(szPath, "\\");
			strcat(szPath, _fileInfo->szName);

			TTask* task = new TTask();
			task->listVolume = this;
			task->iFileRefNum = _fileInfo->iFileReferenceNumber;
			task->szName = new CHAR[strlen(szPath) + 1];
			strcpy(task->szName, szPath);
			PostTask(task);

			if (fileInfo->bFullPath)
				_fileInfo->bFullPath = TRUE;

			delete _fileInfo;
		}
		else
		{
			Log("GetFilePath() Not find parent. %I64d %s", _fileInfo->iParentFileReferenceNumber, _fileInfo->szName);
		}
	}

	TTask* task = new TTask();
	task->iType = 1;
	task->listVolume = this;
	task->iFileRefNum = 0;
	PostTask(task);
}

int CListVolume::ListVolume(TParamList& _param)
{
	WCHAR wszName[PATH_LEN] = { 0 };
	UInt64 iTotal = 0;

	CHAR szVolume[3] = { 0 };
	strcpy(szVolume, _param.szVolume);

	//
	CHAR szVolumePath[NAME_LEN] = { 0 };
	strcpy(szVolumePath, "\\\\.\\");
	strcat(szVolumePath, szVolume);

	HANDLE hDrive = CreateFileA(szVolumePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);

	MFT_ENUM_DATA_V0 med = { 0 };
	med.StartFileReferenceNumber = 0;
	med.LowUsn = 0;
	med.HighUsn = MAXLONGLONG;

	DWORD cb;
	PUSN_RECORD pRecord;

	unsigned char pData[sizeof(DWORDLONG) + 0x10000] = { 0 };

	//
	while (DeviceIoControl(hDrive, FSCTL_ENUM_USN_DATA, &med, sizeof(med), pData, sizeof(pData), &cb, NULL) != FALSE)
	{
		pRecord = (PUSN_RECORD)&pData[sizeof(USN)];
		while ((PBYTE)pRecord < (pData + cb))
		{
			//if (pRecord->FileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY	| FILE_ATTRIBUTE_REPARSE_POINT 
			//	| FILE_ATTRIBUTE_COMPRESSED	| FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_VIRTUAL))
			//{
			//}
			//else
			{
				iTotal++;

				//
				TFileInfo* fileInfo = new TFileInfo;

				//
				strcpy(fileInfo->szVolume, szVolume);

				fileInfo->iParentFileReferenceNumber = pRecord->ParentFileReferenceNumber;
				fileInfo->iFileReferenceNumber = pRecord->FileReferenceNumber;
				fileInfo->iFileAttributes = pRecord->FileAttributes;
				fileInfo->fType = ((pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) ? TFileInfo::FTYPE::eDIR : TFileInfo::FTYPE::eFILE;
					
				UInt iLen = (pRecord->FileNameLength / sizeof(WCHAR)) + 1;					
				wmemset(wszName, 0, iLen);
				wcsncpy(wszName, pRecord->FileName, iLen - 1);
				fileInfo->szName = WChar2Char(wszName);

				if (fileInfo->fType == TFileInfo::FTYPE::eDIR)
				{
					AddFolder(fileInfo);
				}
				else
				{
					AddFile(fileInfo);
				}
			}

			//
			pRecord = (PUSN_RECORD)((PBYTE)pRecord + pRecord->RecordLength);
		}
		med.StartFileReferenceNumber = *(DWORDLONG *)pData;
	}

	{
		BuildFolderPath();
	}

	return 0;
}